/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.glc.hms.core.interceptor;


import cn.hutool.cache.Cache;
import com.alibaba.fastjson.JSONObject;
import com.glc.hms.common.data.ErrorResponseData;
import com.glc.hms.common.exception.ServiceException;
import com.glc.hms.common.util.HttpUtil;
import com.glc.hms.core.shiro.ShiroUser;
import com.glc.hms.core.shiro.ShiroUtil;
import com.glc.hms.holder.ShiroSessionHolder;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;

/**
 * 自定义未登录的情况
 * @author Santa
 * @since 2019/11/14
 */
public class MyUserFilter extends AccessControlFilter {

    /**  同一个帐号最大会话数 默认1 */
    private int maxSession = 1;
    /**  踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 */
    private boolean kickoutAfter = false;
    private SessionManager sessionManager;
    /**  保存用户session信息 */
    private Cache<String,Map<Long, Deque<Serializable>>> cache = ShiroSessionHolder.getCache(maxSession);



    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        Subject subject = getSubject(request, response);
        //放过OPTIONS请求
        //当然 这里也可以对请求做一些其他的预处理  其实在这里也可以实现jwt一样的功能
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
            //获取登陆用户信息和session信息
           Session session = subject.getSession();
           Serializable sessionId = session.getId();
           ShiroUser user = null;
           //如果获取不到 说明已经被干掉了 所以踢掉
           try {
            PrincipalCollection principals = subject.getPrincipals();
            user = (ShiroUser)principals.getPrimaryPrincipal();
            } catch (Exception e) {
            return false;
           }

        Map<Long, Deque<Serializable>> sessionIds = cache.get("Session");

        //获取用户session缓存
            Long userId = user.getUserId();
            Deque<Serializable> deque = sessionIds.get(userId);
            if(deque == null) {
                deque = new LinkedList<Serializable>();
                sessionIds.put(userId, deque);
            }
            //如果队列里没有此sessionId，且用户没有被踢出；放入队列
            if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
                deque.push(sessionId);
            }

            //如果被踢出了，直接退出
            if (session.getAttribute("kickout") != null) {
                //会话被踢出了
                try {
                    ShiroUtil.getSubject().logout();
                } catch (Exception e) {
                }
                //return false;
                throw new ServiceException(500,"你被:"+ HttpUtil.getIp()+"用户挤下线");
            }
            //如果队列里的sessionId数超出最大会话数，开始踢人
            int temp = deque.size();
            while(temp > maxSession) {
                Serializable kickoutSessionId = null;
                if(kickoutAfter) {
                    kickoutSessionId=deque.getFirst();;
                    temp = temp -1;
                } else { //否则踢出前者
                    kickoutSessionId = deque.getLast();
                    temp = temp -1;
                }
                try {
                    Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
                    if(kickoutSession != null) {
                        //设置会话的kickout属性表示踢出了
                        kickoutSession.setAttribute("kickout", true);
                    }
                } catch (Exception e) {//ignore exception
                    e.printStackTrace();
                }
            }
            return subject.getPrincipal() != null;

    }

    /**
     * This default implementation simply calls
     * {@link #saveRequestAndRedirectToLogin(ServletRequest, ServletResponse) saveRequestAndRedirectToLogin}
     * and then immediately returns <code>false</code>, thereby preventing the chain from continuing so the redirect may
     * execute.
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        //shirol拦截所有请求,判断登陆情况
        /**
         * 第一次点击页面
         */
        String header = httpServletRequest.getHeader("Referer");
        if (header == null) {
            ErrorResponseData data = new ErrorResponseData(401, "请先登陆！");
            httpServletResponse.getWriter().write(JSONObject.toJSON(data).toString());
            return false;
        } else {
            /**
             * 从别的页面跳转过来的
             */
            if (ShiroUtil.getSession().getAttribute("sessionFlag") == null) {
                ErrorResponseData data = new ErrorResponseData(401, "登陆超时，请重新登陆！");
                httpServletResponse.getWriter().write(JSONObject.toJSON(data).toString());
                return false;
            } else {
                ErrorResponseData data = new ErrorResponseData(233, "恩？搞事情？");
                httpServletResponse.getWriter().write(JSONObject.toJSON(data).toString());
                return false;
            }
        }
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }
    public void setMaxSession(Integer maxSession) {
        this.maxSession = maxSession;
    }
    public void setKickoutAfter(Boolean kickout) {
        this.kickoutAfter = kickout;
    }

}
